package cz.vutbr.fit.dnacompress.encoding;

public class RiceCode {
	private static final int BASE = 7;
	private int size;	// number of items stored
	private int nbits;	// number of bits to store the items
	private int m;		// parameter to this encoding
	private byte[] bits;	// the actual bits

	// add a bit to the encoding

	private void addbit(int b) {
		int len = bits.length - BASE;

		// need to grow the bit list

		if (nbits == len * 8) {
			int newlen = (int)(len * 1.5) + BASE + 1;
			byte tmp[] = new byte[newlen];
			System.arraycopy(bits, 0, tmp, 0, bits.length);
			bits = tmp;
		}
		if (b == 1)
			bits[BASE + (nbits >> 3)] |= (1 << (nbits & 0x7));
		nbits++;
	}

	// get the value of the n-th bit

	private int getbit(int n) {
		return (bits[BASE + (n >> 3)] >> (n & 0x7)) & 0x1;
	}

	// construct a new encoding object

	public RiceCode(int mval) {
		bits = new byte[BASE];
		m = mval;
		if (m < 0 || m > 16)
			throw new RuntimeException("m < 0 || m > 16");
		bits[BASE - 1] = (byte)m;
	}

	// construct a new object from a previously exported encoding

	public RiceCode(byte vec[]) {
		bits = vec;
		int b0 = (bits[0] < 0 ? bits[0] + 0x100 : bits[0]);
		int b1 = (bits[1] < 0 ? bits[1] + 0x100 : bits[1]);
		int b2 = (bits[2] < 0 ? bits[2] + 0x100 : bits[2]);
		int b3 = (bits[3] < 0 ? bits[3] + 0x100 : bits[3]);
		int b4 = (bits[4] < 0 ? bits[4] + 0x100 : bits[4]);
		int b5 = (bits[5] < 0 ? bits[5] + 0x100 : bits[5]);
		size = (b0 << 16) | (b1 << 8) | b2;
		nbits = (b3 << 16) | (b4 << 8) | b5;
		m = bits[BASE - 1];
	}

	// get the number of items

	public int getSize() {
		return size;
	}

	// get the number of bits

	public int getNumBits() {
		return nbits;
	}

	// add an item to the encoding

	public void addItem(int val) {
		if (val < 1)
			throw new IllegalArgumentException("val < 1");
		size++;

		int x = val - 1;
		int q = x >> m;
		int r = x & ((1 << m) - 1);

		// encode the first (unary) part

		while (q-- > 0)
			addbit(1);
		addbit(0);

		// encode the binary part

		if (m > 0) {
			int mask = (1 << (m - 1));
			while (mask != 0) {
				addbit((r & mask) != 0 ? 1 : 0);
				mask >>= 1;
			}
		}
	}

	// get the items in this encoding and return them in a vector

	public int[] getItems() {
		int items[] = new int[size];
		int currbit = 0;
		for (int i = 0; i < size; i++) {
			int unary = 0;
			while (getbit(currbit) != 0) {
				unary++;
				currbit++;
			}
			currbit++;
			int binary = 0;
			for (int j = 1; j <= m; j++)
				binary = (binary << 1) | getbit(currbit++);
			items[i] = (unary << m) + binary + 1;
		}
		return items;
	}

	// export the encoding into a vector of bytes

	public byte[] getBits() {
		bits[0] = (byte)(size >> 16);
		bits[1] = (byte)((size >> 8) & 0xff);
		bits[2] = (byte)(size & 0xff);
		bits[3] = (byte)(nbits >> 16);
		bits[4] = (byte)((nbits >> 8) & 0xff);
		bits[5] = (byte)(nbits & 0xff);
		int len = BASE + (nbits + 7) / 8;
		byte tmp[] = new byte[len];
		System.arraycopy(bits, 0, tmp, 0, len); 
		return tmp;
	}

	// driver

	public static void main(String args[]) {
		java.util.Random rn = new java.util.Random(0);
		for (int i = 0; i <= 16; i++) {
			RiceCode rc = new RiceCode(i);

			// add 1000 numbers, with 5% of them in the
			// range 1-1000, 95% in the range 1-25
			StringBuffer sb = new StringBuffer(1000);
			
			for (int j = 1; j <= 1000; j++) {
				int num;
				if (rn.nextInt(20) == 0)
					num = rn.nextInt(1000) + 1;
				else
					num = rn.nextInt(25) + 1;
				rc.addItem(num);
				sb.append( num);
			}

			// print out the number of bits used
			System.out.println( sb.length());
			System.out.println(i + " " + rc.getNumBits());
		}
	}
}
